WebSocket
1.1 介绍
WebSocket是基于TCP的一种新的网络协议,它实现了浏览器与服务器之间的全双工通信—浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
1.2 应用场景
需要长连接、全双工通信的场景:
- 视频弹幕
- 网页聊天
- 体育实况更新
- 股票基金报价实时更新
你好,Ethan!既然你已经掌握了 Spring Task(单向推送/定时调度)和 SQL 批量处理,学习 WebSocket 会非常顺感,因为它是解决“实时性”问题的终极方案。
在传统的 HTTP 协议中,服务器像是个“被动”的柜员,只有你问他(发送请求),他才回答(返回响应)。而 WebSocket 建立了一个全双工(Full-duplex)**的持久连接,服务器一旦发现订单超时,可以**主动把消息“拍”到前端屏幕上,而不需要前端不停地轮询。
1.3 WebSocket 的核心流程
WebSocket 的生命周期分为三个阶段:
- 握手(Handshake):客户端通过 HTTP 发起申请,请求升级协议。
- 连接(Established):一旦升级成功,HTTP 连接关闭,取而代之的是一个持久的 TCP 通道。
- 双向传输:双方可以随时发送数据,不需要再携带繁琐的 HTTP Header。
1.4 在 Spring Boot 中快速上手
在 Spring Boot 中使用 WebSocket,通常需要三步走:
第一步:引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>第二步:编写配置类
你需要定义一个 ServerEndpointExporter,它会自动注册你编写的 WebSocket 端点。
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}第三步:编写端点服务(核心类)
使用 @ServerEndpoint 注解定义连接地址,并处理连接、关闭和消息逻辑。
@Component
@ServerEndpoint("/ws/{clientId}") // 路径参数,比如订单系统的客户ID
@Slf4j
public class WebSocketServer {
// 用来存放所有在线客户端的会话,注意要用线程安全的 Map
private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam("clientId") String clientId) {
log.info("客户端连接成功: {}", clientId);
sessionMap.put(clientId, session);
}
@OnClose
public void onClose(@PathParam("clientId") String clientId) {
log.info("客户端断开连接: {}", clientId);
sessionMap.remove(clientId);
}
/**
* 服务器向指定客户端推送消息
*/
public void sendToClient(String clientId, String message) {
Session session = sessionMap.get(clientId);
if (session != null) {
try {
session.getBasicRemote().sendText(message);
} catch (Exception e) {
log.error("推送到客户端 {} 失败", clientId, e);
}
}
}
}1.5 联动实战:Spring Task + WebSocket
Ethan,结合你刚才写的“超时订单取消”任务,完美的实时交互应该是这样的:
当你的 Spring Task 扫描到订单超时并取消后,立刻通过 WebSocket 给前端发个弹窗提醒,而不是等用户刷新页面。
@Scheduled(cron = "0 * * * * ?")
public void rejectTimeoutOrder() {
List<Long> orderIds = ordersMapper.getTimeoutOrder();
if (!CollectionUtils.isEmpty(orderIds)) {
// 1. 批量修改数据库状态
ordersMapper.updateBatch(orderIds, 6, "超时自动取消");
// 2. 实时通知用户(假设我们有 userId)
for (Long id : orderIds) {
String userId = getUserIdByOrderId(id); // 伪代码
webSocketServer.sendToClient(userId, "您的订单 " + id + " 因超时已自动取消");
}
}
}1.6 关键避坑指南(Ethan 必读)
Bean 注入问题:
由于 WebSocket 的 Endpoint 是由容器自动创建的(多例模式),如果你在
WebSocketServer里直接@Autowired一个Mapper,可能会发现它是 null。- 解决:通常使用
static变量注入,或者通过ApplicationContext手动获取。
- 解决:通常使用
集群/分布式问题:
如果你的应用部署了两台服务器 A 和 B。用户连在 A 上,但 Spring Task 在 B 上跑,B 是找不到 A 上的 Session 的。
- 解决:这种情况下需要引入 Redis 的发布/订阅(Pub/Sub) 模式。B 发消息到 Redis,A 监听到后再推给用户。
心跳机制(Heartbeat):
网络不稳定或防火墙可能会悄悄切断长连接。建议前端每隔 30 秒发一个
ping,后端回一个pong,确保连接还活着。
1.7 总结
| 维度 | HTTP 轮询 | WebSocket |
|---|---|---|
| 实时性 | 延迟取决于轮询间隔 | 准实时 |
| 服务器压力 | 高(频繁建立连接、发送 Header) | 低(连接后只传数据) |
| 适用场景 | 报表刷新、低频更新 | 聊天、实时弹窗、股票走势 |